﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Xml;
using System.IO;
using System.Diagnostics;

namespace Microsoft.Popfly.GameCreator.Data
{

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
    public class GameXamlProperty : System.Attribute
    {
        public GameXamlProperty()
        {
            this.ns = null;
            this.xamlMappingType = XamlType.undefined;
            this.nodeName = null;
            this.objectType = null;
        }
        public enum XamlType
        {
            undefined = 0,
            /// <summary>
            /// If this property is an attribute on the current element
            /// </summary>
            attribute,
            /// <summary>
            /// If this property is an element containing only a textnode
            /// </summary>
            simple,
            /// <summary>
            /// If this property is a reference to an existing object located somewhere else in the game data
            /// </summary>
            reference,
            /// <summary>
            /// If this property is another complex class containing other properties or elements
            /// </summary>
            complex,
            /// <summary>
            /// If this property is the content on the current element
            /// </summary>
            content,
            dictionary,
            list,
            resources
        }
        public XamlType xamlMappingType;
        public XamlType dictionaryEntryMappingType;
        /// <summary>
        /// Which namespace this attribute is valid for, if null this attribute is valid for all namespaces
        /// </summary>
        public String ns;
        /// <summary>
        /// Element name or attribute name in xml that maps to this property, if null it is the field name
        /// </summary>
        public String nodeName;
        public Type objectType;
        public Type dictionaryContainerType;
        public String dictionaryEntryNodeName;
        public String dictionaryHashAttributeName;
        public String attributeName;
        public String attributeValue;
    }
    //TODO: the whole array thing can use a bit of a cleanup
    public class GameXamlDeserializer
    {

        private class EntryHandler
        {
            public interface IEntryInfo
            {
                void SetValue(Object setObject, Object setValue);
                Object GetValue(Object obj);
                Type Type { get; }
                String Name { get; }
            }
            private class FieldEntryInfo : IEntryInfo
            {
                private FieldInfo memberInfo;
                public FieldEntryInfo(FieldInfo memberInfo)
                {
                    this.memberInfo = memberInfo;
                }
                public void SetValue(Object setObject, Object setValue)
                {
                    memberInfo.SetValue(setObject, setValue);
                }
                public Object GetValue(Object obj)
                {
                    return memberInfo.GetValue(obj);
                }
                public Type Type
                {
                    get
                    {
                        return memberInfo.FieldType;
                    }
                }
                public String Name
                {
                    get
                    {
                        return memberInfo.Name;
                    }
                }
            }
            private class PropertyEntryInfo : IEntryInfo
            {
                private System.Reflection.PropertyInfo memberInfo;
                public PropertyEntryInfo(System.Reflection.PropertyInfo memberInfo)
                {
                    this.memberInfo = memberInfo;
                }
                public void SetValue(Object setObject, Object setValue)
                {
                    memberInfo.SetValue(setObject, setValue, null);
                }
                public Object GetValue(Object obj)
                {
                    return memberInfo.GetValue(obj, null);
                }
                public Type Type
                {
                    get
                    {
                        return memberInfo.PropertyType;
                    }
                }
                public String Name
                {
                    get
                    {
                        return memberInfo.Name;
                    }
                }
            }

            public GameXamlProperty.XamlType mappingType;
            public GameXamlProperty.XamlType dictionaryEntryMappingType;
            public Type dictionaryContainerType;
            public String name;
            public Type objectType;
            public String dictionaryEntryNodeName;
            public String dictionaryHashAttributeName;
            public String ns;
            public IEntryInfo memberInfo;
            public String attributeName;
            public String attributeValue;
            public bool isValid(XmlReader reader)
            {

                return (
                    (ns == null || ns == reader.NamespaceURI) &&
                    (
                        (reader.NodeType == XmlNodeType.Attribute && this.mappingType == GameXamlProperty.XamlType.attribute) ||
                        (reader.NodeType == XmlNodeType.Element && this.mappingType != GameXamlProperty.XamlType.attribute)
                    ) &&
                    (
                        this.mappingType == GameXamlProperty.XamlType.dictionary ||
                        this.attributeName == null ||
                        reader.GetAttribute(attributeName) == attributeValue
                    ) &&
                    (reader.LocalName == this.name)
                   );
            }
            public bool isValidArrayObject(XmlReader reader)
            {
                return (
                        (this.mappingType == GameXamlProperty.XamlType.dictionary || this.mappingType == GameXamlProperty.XamlType.list) &&
                        (this.attributeName == null ||
                        reader.GetAttribute(attributeName) == attributeValue) &&
                        this.dictionaryEntryNodeName == reader.LocalName
                   );
            }
            public static EntryHandler ConstructEntry(MemberInfo fieldInfo, GameXamlProperty attribute)
            {
                IEntryInfo memberInfo = (fieldInfo is FieldInfo ? (IEntryInfo)(new FieldEntryInfo((FieldInfo)fieldInfo)) : (IEntryInfo)(new PropertyEntryInfo((System.Reflection.PropertyInfo)fieldInfo)));

                GameXamlProperty.XamlType mappingType = (attribute.xamlMappingType == GameXamlProperty.XamlType.undefined ?
                        (memberInfo.Type.GetInterface("IDictionary", false) != null ?
                            GameXamlProperty.XamlType.dictionary :
                                (
                                    memberInfo.Type.GetInterface("IList", false) != null ?
                                    GameXamlProperty.XamlType.list :
                                    GameXamlProperty.XamlType.attribute
                                )
                            )
                        : attribute.xamlMappingType);

                String arrayElementName = null;


                if (mappingType == GameXamlProperty.XamlType.dictionary)
                {
                    if (attribute.objectType == null)
                        throw new NullReferenceException("objectType needs to be defined for dictionary types.");
                    if (memberInfo.Type.GetInterface("IDictionary", false) == null)
                        throw new InvalidCastException("dictionary types need to implement the IDictionary interface");
                    arrayElementName = (attribute.dictionaryEntryNodeName == null ? attribute.objectType.Name : attribute.dictionaryEntryNodeName);

                }
                else if (mappingType == GameXamlProperty.XamlType.list)
                {
                    if (attribute.objectType == null)
                        throw new NullReferenceException("objectType needs to be defined for list types.");
                    if (memberInfo.Type.GetInterface("IList", false) == null)
                        throw new InvalidCastException("list types need to implement the IList interface");
                    arrayElementName = (attribute.dictionaryEntryNodeName == null ? attribute.objectType.Name : attribute.dictionaryEntryNodeName);
                }

                return new EntryHandler
                {
                    mappingType = mappingType,
                    dictionaryEntryMappingType = (attribute.dictionaryEntryMappingType == GameXamlProperty.XamlType.undefined ? GameXamlProperty.XamlType.complex : attribute.dictionaryEntryMappingType),
                    name = (attribute.nodeName == null ? memberInfo.Name : attribute.nodeName),
                    dictionaryEntryNodeName = arrayElementName,
                    objectType = (attribute.objectType == null ? memberInfo.Type : attribute.objectType),
                    dictionaryContainerType = (attribute.dictionaryContainerType == null ? memberInfo.Type : attribute.dictionaryContainerType),
                    ns = attribute.ns,
                    memberInfo = memberInfo,
                    dictionaryHashAttributeName = (attribute.dictionaryHashAttributeName == null ? "Name" : attribute.dictionaryHashAttributeName),
                    attributeName = attribute.attributeName,
                    attributeValue = attribute.attributeValue
                };
            }
        }
        private static void DeserializeArray(Object deserializedObject, XmlReader reader, IEnumerable<EntryHandler> entries)
        {
            if (!reader.IsEmptyElement)
            {
                reader.ReadStartElement();
                {
                    while (
                        reader.NodeType != XmlNodeType.EndElement &&
                        reader.NodeType != XmlNodeType.None)
                    {

                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            IEnumerable<EntryHandler> arrayEntries =
                                (from arrayEntry in entries where arrayEntry.isValidArrayObject(reader) select arrayEntry);
                            if (arrayEntries.Count() > 0)
                            {
                                EntryHandler entry = arrayEntries.First();
                                if (entry.dictionaryEntryMappingType == GameXamlProperty.XamlType.simple)
                                {
                                    if (entry.objectType.IsEnum)
                                    {
                                        if (deserializedObject.GetType().GetInterface("IDictionary", true) != null)
                                        {
                                            entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                            deserializedObject,
                                                            new Object[] { reader.GetAttribute(entry.dictionaryHashAttributeName), Enum.Parse(entry.objectType, reader.ReadElementContentAsString(), true) }
                                                        );
                                        }
                                        else
                                        {
                                            entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                            deserializedObject,
                                                            new Object[] { Enum.Parse(entry.objectType, reader.ReadElementContentAsString(), true) }
                                                        );
                                        }
                                    }
                                    else
                                    {
                                        if (deserializedObject.GetType().GetInterface("IDictionary", true) != null)
                                        {
                                            entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                        deserializedObject,
                                                        new Object[] { reader.GetAttribute(entry.dictionaryHashAttributeName), reader.ReadElementContentAs(entry.objectType, null) }
                                                    );
                                        }
                                        else
                                        {
                                            entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                        deserializedObject,
                                                        new Object[] { reader.ReadElementContentAs(entry.objectType, null) }
                                                    );
                                        }
                                    }
                                    continue;
                                }
                                else if (entry.dictionaryEntryMappingType == GameXamlProperty.XamlType.dictionary || entry.dictionaryEntryMappingType == GameXamlProperty.XamlType.list)
                                {
                                    Object dictionaryObject = entry.dictionaryContainerType.GetConstructor(new System.Type[] { }).Invoke(new object[] { });
                                    string HashAttribute = reader.GetAttribute(entry.dictionaryHashAttributeName);
                                    DeserializeArray(dictionaryObject, reader, entries.ToArray());

                                    if (deserializedObject.GetType().GetInterface("IDictionary", true) != null)
                                    {
                                        entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                deserializedObject, new Object[] { HashAttribute, dictionaryObject }
                                            );
                                    }
                                    else
                                    {
                                        entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                deserializedObject, new Object[] { dictionaryObject }
                                            );
                                    }

                                    continue;
                                }
                                else
                                {
                                    String hashName = reader.GetAttribute(entry.dictionaryHashAttributeName);
                                    Object dictionaryObject = Deserialize(reader, entry.objectType);

                                    if (deserializedObject.GetType().GetInterface("IDictionary", true) != null)
                                    {
                                        entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                deserializedObject,
                                                new Object[] { hashName, dictionaryObject }
                                            );
                                    }
                                    else
                                    {
                                        entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                                deserializedObject,
                                                new Object[] { dictionaryObject }
                                            );
                                    }

                                    continue;
                                }

                            }
                            else
                            {
                                reader.Skip();
                            }
                        }
                        else
                        {
                            reader.Read();
                        }
                    }
                    reader.ReadEndElement();
                }
            }
            else
            {
                reader.Read();
            }
        }
        public static Object Deserialize(XmlReader reader, Type type)
        {
            MemberInfo[] memInfos = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.GetProperty);


            IEnumerable<EntryHandler> propEntries =
                (from memInfo in memInfos
                 from attribute in (GameXamlProperty[])memInfo.GetCustomAttributes(typeof(GameXamlProperty), false)
                 select EntryHandler.ConstructEntry(memInfo, attribute));

            Object deserializedObject = type.GetConstructor(new System.Type[] { }).Invoke(new object[] { });


            if (reader.NodeType == XmlNodeType.None)
                reader.Read();

            for (int x = 0; x < reader.AttributeCount; ++x)
            {
                reader.MoveToAttribute(x);
                IEnumerable<EntryHandler> entries =
                    (from entry in propEntries where entry.isValid(reader) select entry);
                if (entries.Count() > 0)
                {
                    EntryHandler entry = entries.First();
                    if (entry.objectType.IsEnum)
                    {
                        try
                        {
                            entry.memberInfo.SetValue(
                                        deserializedObject,
                                        Enum.Parse(entry.objectType, reader.ReadContentAsString(), true)
                                        );
                        }
                        catch (Exception) //If casting the object fails, we don't throw all the way to the caller.
                        {
                            //Do nothing
                        }
                    }
                    else
                    {
                        try
                        {
                            object value;
                            if (entry.objectType.Name.Equals("Double"))
                            {
                                double valueDouble;
                                if (double.TryParse(reader.Value, out valueDouble))
                                {
                                    value = valueDouble;
                                }
                                else
                                {
                                    value = default(double);
                                }
                            }
                            else if (entry.objectType.Name.Equals("Boolean"))
                            {
                                bool valueBool;
                                if (bool.TryParse(reader.Value, out valueBool))
                                {
                                    value = valueBool;
                                }
                                else
                                {
                                    value = default(bool);
                                }
                            }
                            else
                            {
                                value = reader.ReadContentAs(entry.objectType, null);
                            }

                            entry.memberInfo.SetValue(
                                        deserializedObject,
                                        value
                                    );
                        }
                        catch (XmlException) //If casting the object fails, we don't throw all the way to the caller.
                        {
                            //Do nothing
                        }
                    }
                    continue;
                }
            }

            reader.MoveToElement();

            if (!reader.IsEmptyElement)
            {
                reader.ReadStartElement();
                while (
                    reader.NodeType != XmlNodeType.EndElement &&
                    reader.NodeType != XmlNodeType.None)
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        IEnumerable<EntryHandler> entries =
                            (from entry in propEntries where entry.isValid(reader) select entry);
                        if (entries.Count() > 0)
                        {
                            EntryHandler entry = entries.First();
                            if (entry.mappingType == GameXamlProperty.XamlType.simple)
                            {
                                if (entry.objectType.IsEnum)
                                {
                                    entry.memberInfo.SetValue(
                                                deserializedObject,
                                                Enum.Parse(entry.objectType, reader.ReadElementContentAsString(), true)
                                                );
                                }
                                else
                                {
                                    entry.memberInfo.SetValue(
                                                deserializedObject,
                                                reader.ReadElementContentAs(entry.objectType, null)
                                            );
                                }
                                continue;
                            }
                            else if (entry.mappingType == GameXamlProperty.XamlType.dictionary || entry.mappingType == GameXamlProperty.XamlType.list)
                            {
                                Object dictionaryObject = entry.dictionaryContainerType.GetConstructor(new System.Type[] { }).Invoke(new object[] { });
                                DeserializeArray(dictionaryObject, reader, entries.ToArray());
                                entry.memberInfo.SetValue(deserializedObject, dictionaryObject);
                                continue;
                            }
                            else if (entry.mappingType == GameXamlProperty.XamlType.resources)
                            {
                                Object dictionaryObject = entry.memberInfo.GetValue(deserializedObject);
                                if (dictionaryObject == null)
                                {
                                    dictionaryObject = entry.dictionaryContainerType.GetConstructor(new System.Type[] { }).Invoke(new object[] { });
                                }
                                entry.dictionaryContainerType.GetMethod("Add").Invoke(
                                        dictionaryObject,
                                        new Object[] { reader.GetAttribute(entry.dictionaryHashAttributeName), reader.ReadOuterXml() }
                                    );
                                entry.memberInfo.SetValue(deserializedObject, dictionaryObject);
                                continue;
                            }
                            else
                            {
                                entry.memberInfo.SetValue(
                                            deserializedObject,
                                            Deserialize(reader, entry.objectType)
                                        );
                                continue;
                            }
                        }
                        else
                        {
                            reader.Skip();
                        }
                    }
                    else if (reader.NodeType == XmlNodeType.Text)
                    {
                        IEnumerable<EntryHandler> contentEntries = (from entry in propEntries where entry.mappingType == GameXamlProperty.XamlType.content select entry);
                        foreach (EntryHandler contentEntry in contentEntries)
                        {
                            contentEntry.memberInfo.SetValue(
                                deserializedObject,
                                reader.ReadContentAs(contentEntry.objectType, null)
                                );
                        }
                        if (contentEntries.Count() <= 0)
                        {
                            reader.Read();
                        }
                    }
                    else
                    {
                        reader.Read();
                    }

                }
                if (reader.NodeType == XmlNodeType.EndElement)
                    reader.ReadEndElement();
            }
            else
            {
                reader.Read();
            }
            return deserializedObject;
        }
    }
}